package jmine.tec.aom.domain.impl;

import java.lang.reflect.Method;

import jmine.tec.aom.domain.AbstractPropertyValue;
import jmine.tec.aom.domain.Property;
import jmine.tec.aom.exception.FillException;
import jmine.tec.aom.util.PropertyValueUtils;
import jmine.tec.utils.reflection.fast.FastMethodNoArgs;
import jmine.tec.utils.reflection.fast.FastMethodSingleArgument;
import jmine.tec.utils.reflection.fast.reflection.FastMethodImpl;

import org.hibernate.proxy.HibernateProxy;

/**
 * Defines a synthetic {@link AbstractPropertyValue}. This implementation has no knowledge of the actual type represented by the associated
 * {@link Property}.
 * <p>
 * All it knows is that the value should be retrieved from some target {@link Object} through the use of the supplied getter {@link Method}
 * and set on the target through use of the supplied setter {@link Method}.
 * <p>
 * For correct behavior the methods {@link #setValue(String)} and {@link #getValueAsString()} of the appropriate
 * {@link AbstractPropertyValue} for the type of the related {@link Property} must be correctly implemented.
 * <p>
 * For instance, in order to represent a {@link Double} through a {@link SyntheticPropertyValue} the class {@link DoublePropertyValue} must
 * implement those methods correctly.
 * <p>
 * This means you can only use {@link SyntheticPropertyValue} for types that have a corresponding {@link AbstractPropertyValue}
 * implementation.
 * 
 * @author jrenaut
 */
public class SyntheticPropertyValue extends AbstractPropertyValue<Object> {

    private final Object target;

    private final FastMethodNoArgs getterMethod;

    private final FastMethodSingleArgument setterMethod;

    private PropertyValueUtils propertyValueUtils;

    /**
     * @param propertyValueUtils a {@link PropertyValueUtils}
     * @param property a {@link Property}
     * @param target the target from which the actual value should be retrieved and on which it should be set
     * @param getterMethod the getter {@link FastMethodNoArgs}
     * @param setterMethod the setter {@link FastMethodSingleArgument}
     */
    public SyntheticPropertyValue(PropertyValueUtils propertyValueUtils, Property property, Object target, FastMethodNoArgs getterMethod,
            FastMethodSingleArgument setterMethod) {
        this.target = target;
        this.getterMethod = getterMethod;
        this.setterMethod = setterMethod;
        this.propertyValueUtils = propertyValueUtils;
        this.setProperty(property);
    }

    /**
     * @param propertyValueUtils a {@link PropertyValueUtils}
     * @param property a {@link Property}
     * @param target the target from which the actual value should be retrieved and on which it should be set
     * @param getterMethod the getter {@link Method}
     * @param setterMethod the setter {@link Method}
     */
    public SyntheticPropertyValue(PropertyValueUtils propertyValueUtils, Property property, Object target, Method getterMethod,
            Method setterMethod) {
        this(propertyValueUtils, property, target, new FastMethodImpl(getterMethod), new FastMethodImpl(setterMethod));
    }

    /**
     * Retorna o target (inicializado caso seja um HibernateProxy).
     * 
     * @return Object
     */
    private Object getTarget() {
        if (this.target instanceof HibernateProxy) {
            return ((HibernateProxy) this.target).getHibernateLazyInitializer().getImplementation();
        }
        return this.target;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected AbstractPropertyValue<Object> createBean() {
        return new SyntheticPropertyValue(this.propertyValueUtils, this.getProperty(), this.target, this.getterMethod, this.setterMethod);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void doSetValue(String stringValue) throws FillException {
        this.setValue(this.propertyValueUtils.createPropertyValue(this.getProperty(), stringValue).getValue());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @SuppressWarnings("unchecked")
    protected String doGetValueAsString() {
        try {
            AbstractPropertyValue pv = this.propertyValueUtils.createPropertyValue(this.getProperty(), null);
            pv.setValue(this.getValue());
            return pv.getValueAsString();
        } catch (FillException e) {
            throw new IllegalStateException("Unable to set null value on AbstractPropertyValue for Property " + this.getPropertyName(), e);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object getValue() {
        try {
            return this.getterMethod.invokeNoArgs(this.target);
        } catch (Exception e1) {
            throw new IllegalStateException(e1);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setValue(Object value) {
        try {
            this.setterMethod.invokeSingleArgument(this.getTarget(), value);
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isSynthetic() {
        return true;
    }
}
